#include "application.h"


DWORD WINAPI display_thread(LPVOID thread_param)
{
    Application *app = (Application *)thread_param;

    char input_str[512];
    memset(input_str, 0, 512);

    while (app->m_Runner->IsRunning()) {
        std::cin.getline(input_str, 512, '\n');

        app->on_process(input_str);
    }
    
    return 0;
}

void Application::on_cleanup()
{
    if (m_LogFile.good()) {
        m_LogFile << getTimeString().GetCharString() << "destroying awesomium stuff" << std::endl;
        m_LogFile.flush();
    }
    
    m_Tab->Destroy();
    m_Session->Release();
    m_Core->Shutdown();
}
void Application::on_close()
{
}
void Application::on_flash_window()
{
    FLASHWINFO flash_info;
    flash_info.cbSize = sizeof(FLASHWINFO);
    flash_info.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG;
    flash_info.dwTimeout = 0;
    flash_info.hwnd = m_Window;
    flash_info.uCount = 3;

    FlashWindowEx(&flash_info);
}
void Application::on_init()
{
    m_Core = WebCore::Initialize(WebConfig());

    if (m_LogFile.good()) {
        m_LogFile << getTimeString().GetCharString() << "creating awesomium core" << std::endl;
        m_LogFile.flush();
    }

    if (!m_Core || !m_Core->instance()) {
        MessageBoxA(0, "Error", "Can not initialize Awesomium.", MB_ICONERROR);

        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "creating awesomium core FAILED" << std::endl;
            m_LogFile.flush();
        }

        return;
    }

    m_Session = m_Core->CreateWebSession(WSLit("data"), WebPreferences());
    m_Tab = m_Core->CreateWebView(1280, 720, m_Session, kWebViewType_Offscreen);

    if (m_LogFile.good()) {
        m_LogFile << getTimeString().GetCharString() << "creating awesomium session and tab" << std::endl;
        m_LogFile.flush();
    }

    m_DisplayThread = CreateThread(0, 0, display_thread, this, CREATE_SUSPENDED, 0);
    m_Runner = new TL::Runner(true);
    m_TimeStamp = 0;

    if (m_LogFile.good()) {
        m_LogFile << getTimeString().GetCharString() << "creating display thread" << std::endl;
        m_LogFile.flush();
    }

    SetConsoleTitleA("Steam Bot :: Core");

    m_Window = GetConsoleWindow();

    if (!m_Window) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "can not get window handle" << std::endl;
            m_LogFile.flush();
        }
    }

    if (m_LogFile.good()) {
        m_LogFile << getTimeString().GetCharString() << "reading cookie.txt" << std::endl;
        m_LogFile.flush();
    }

    on_read_cookie_file();
}
void Application::on_process(TL::String input_str)
{
    TL::SingleList<TL::String> tokens;
    int start = 0;
    int end = input_str.FindFirstSubstring("|", start);

    while (end != -1) {
        tokens.Insert(input_str.GetSubstring(end - start, start));

        start = end + 1;
        end = input_str.FindFirstSubstring("|", start);
    }

    end = input_str.Size();

    tokens.Insert(input_str.GetSubstring(end - start, start));

    if (tokens[0] == "a" && tokens.Size() == 4) {
        m_Anchors.Insert(tokens[1], new Anchor(tokens[1], tokens[2], atoi(tokens[3].GetCharString()), &m_AnchorQueue));
    }
    else if (tokens[0] == "h") {
        std::cout << "a|<query>|<purchase price>|<number of purchases>" << std::endl;
        //std::cout << "a|<query>|<sale price>|<min profit>|<number of purchases>" << std::endl;
        std::cout << "l" << std::endl;
        std::cout << "q" << std::endl;
        std::cout << "v" << std::endl;
    }
    else if (tokens[0] == "l" && m_PurchaseState != PurchaseState::CheckForSuccess) { // NON THREAD-SAFE ! ! !
        for (int i = 0; i < 80; i++) std::cout << '=';
        
        for (int i = 0; i < m_Anchors.Size(); i++) {
            Anchor *current_anchor = m_Anchors.GetByIndex(i);

            std::cout << current_anchor->GetSearchQuery().GetCharString() << "|" << current_anchor->GetTargetPrice().GetNumber() << " [" << current_anchor->GetRemainingPurchases() << "]" << std::endl;
        }

        for (int i = 0; i < 80; i++) std::cout << '=';
    }
    else if (tokens[0] == "q") {
        m_Runner->Stop();
    }
    else if (tokens[0] == "v") {
        std::cout << "build 080" << std::endl;
    }
}
void Application::on_read_cookie_file()
{
    std::ifstream file("cookie.txt", std::ifstream::in);

    if (file.good()) {
        char temp_str[128];

        file.getline(temp_str, 128);

        Anchor::m_CookieString = temp_str;

        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "read from cookie.txt: " << temp_str << std::endl;
            m_LogFile.flush();
        }
    }
    else {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "reading cookie.txt FAILED" << std::endl;
            m_LogFile.flush();
        }

        MessageBoxA(0, "File cookie.txt can not be read.", "Error", MB_ICONERROR);
    }

    file.close();
}
void Application::on_start()
{
    ResumeThread(m_DisplayThread);
}
void Application::on_update()
{
    if (!m_Runner->IsRunning()) {
        return;
    }
    
    if (m_Core && m_Core->instance()) {
        m_Core->Update();
    }

    if (m_PurchaseState == PurchaseState::ReadyToPurchase) {
        if (!m_ActiveAnchor && !m_AnchorQueue.IsEmpty()) {
            m_ActiveAnchor = m_AnchorQueue.Pop();

            m_Tab->LoadURL(WebURL(WSLit(m_ActiveAnchor->GetMarketURL().GetCharString())));

            m_PurchaseState = PurchaseState::LoadingMarketPage;

            if (m_LogFile.good()) {
                m_LogFile << getTimeString().GetCharString() << "retrieving anchor: " << m_ActiveAnchor->GetSearchQuery().GetCharString() << std::endl;
                m_LogFile.flush();
            }
        }
    }
    else if (m_PurchaseState == PurchaseState::LoadingMarketPage) {
        if (!m_Tab->IsLoading()) {
            m_PurchaseState = PurchaseState::MarketPageLoaded;
        }
    }
    else if (m_PurchaseState == PurchaseState::MarketPageLoaded) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "market page loaded" << std::endl;
            m_LogFile.flush();
        }

        char temp_buffer[512];
        TL::String temp_str(512);
        TL::Price item_price, wallet_balance;
        JSValue js_value;

        js_value = m_Tab->ExecuteJavascriptWithResult(WSLit("window.document.getElementsByClassName('market_listing_table_message')[0].innerHTML;"), WSLit(""));
        js_value.ToString().ToUTF8(temp_buffer, 512);
        temp_str = temp_buffer;

        if (temp_str.FindFirstSubstring("There was an error") != -1) {
            if (m_LogFile.good()) {
                m_LogFile << getTimeString().GetCharString() << "market page loaded with ERROR, reloading" << std::endl;
                m_LogFile.flush();
            }

            m_ErrorCount++;

            if (m_ErrorCount < 5) {
                m_Tab->LoadURL(WebURL(WSLit(m_ActiveAnchor->GetMarketURL().GetCharString())));

                m_PurchaseState = PurchaseState::LoadingMarketPage;
            }
            else {
                m_ErrorCount = 0;

                m_PurchaseState = PurchaseState::RestartingAnchor;
            }

            return;
        }

        js_value = m_Tab->ExecuteJavascriptWithResult(WSLit("window.document.getElementsByClassName('market_listing_price market_listing_price_with_fee')[0].innerHTML;"), WSLit(""));
        js_value.ToString().ToUTF8(temp_buffer, 512);
        item_price = TL::Price::ExtractPrice(temp_buffer);

        js_value = m_Tab->ExecuteJavascriptWithResult(WSLit("window.document.getElementById('marketWalletBalanceAmount').innerHTML;"), WSLit(""));
        js_value.ToString().ToUTF8(temp_buffer, 512);
        wallet_balance = TL::Price::ExtractPrice(temp_buffer);

        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "wallet balance: " << wallet_balance.GetNumber() << std::endl;
            m_LogFile << getTimeString().GetCharString() << "item price: " << item_price.GetNumber() << std::endl;
            m_LogFile << getTimeString().GetCharString() << "target price: " << m_ActiveAnchor->GetTargetPrice().GetNumber() << std::endl;
            m_LogFile.flush();
        }

        // проверка цены предмета + проверка баланса
        if (item_price.GetNumber() > 0.0 && item_price.GetNumber() <= m_ActiveAnchor->GetTargetPrice().GetNumber() && item_price.GetNumber() <= wallet_balance.GetNumber()) {
            js_value = m_Tab->ExecuteJavascriptWithResult(WSLit("window.document.getElementsByClassName('item_market_action_button item_market_action_button_green')[0].attributes.getNamedItem('href').value;"), WSLit(""));
            js_value.ToString().ToUTF8(temp_buffer, 512);

            // преобразование js функции
            temp_str = temp_buffer;
            temp_str.Remove("javascript:");
            temp_str += ";";

            m_Tab->ExecuteJavascript(WSLit(temp_str.GetCharString()), WSLit(""));

            m_TimeStamp = GetTickCount();

            m_PurchaseState = PurchaseState::Waiting100msBeforeOpeningConfirmationDialog;
        }
        else {
            m_PurchaseState = PurchaseState::MoneyCheckFail;
        }
    }
    else if (m_PurchaseState == PurchaseState::Waiting100msBeforeOpeningConfirmationDialog) {
        if (GetTickCount() - m_TimeStamp > 100) {
            m_PurchaseState = PurchaseState::ReadyToConfirmPurchase;
        }
    }
    else if (m_PurchaseState == PurchaseState::ReadyToConfirmPurchase) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "accepting the rules" << std::endl;
            m_LogFile.flush();
        }

        // согласие с правилами
        m_Tab->ExecuteJavascript(WSLit("window.document.getElementById('market_buynow_dialog_accept_ssa').checked = true;"), WSLit(""));

        m_TimeStamp = GetTickCount();

        m_PurchaseState = PurchaseState::Waiting100msBeforeClickingAwesomeButton;
    }
    else if (m_PurchaseState == PurchaseState::Waiting100msBeforeClickingAwesomeButton) {
        if (GetTickCount() - m_TimeStamp > 100) {
            m_PurchaseState = PurchaseState::ReadyToClickAwesomeButton;
        }
    }
    else if (m_PurchaseState == PurchaseState::ReadyToClickAwesomeButton) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "clicking final button" << std::endl;
            m_LogFile.flush();
        }
        
        m_Tab->InjectMouseMove(900, 540);
        m_Tab->InjectMouseDown(MouseButton::kMouseButton_Left);
        m_Tab->InjectMouseUp(MouseButton::kMouseButton_Left);

        m_TimeStamp = GetTickCount();

        m_PurchaseState = PurchaseState::Waiting5000msBeforeCheckingSuccessPurchase;
    }
    else if (m_PurchaseState == PurchaseState::Waiting5000msBeforeCheckingSuccessPurchase) {
        if (GetTickCount() - m_TimeStamp > 5000) {
            m_PurchaseState = PurchaseState::CheckForSuccess;
        }
    }
    else if (m_PurchaseState == PurchaseState::CheckForSuccess) {
        char temp_buffer[512];
        TL::String temp_str(512);
        JSValue js_value;

        js_value = m_Tab->ExecuteJavascriptWithResult(WSLit("window.document.getElementById('market_buynow_dialog_error_text').innerHTML;"), WSLit(""));
        js_value.ToString().ToUTF8(temp_buffer, 512);
        temp_str = temp_buffer;

        if (temp_str.FindFirstSubstring("somebody else has already purchased it") != -1) {
            if (m_LogFile.good()) {
                m_LogFile << getTimeString().GetCharString() << "somebody has already purchased item" << std::endl;
                m_LogFile.flush();
            }

            m_PurchaseState = PurchaseState::RestartingAnchor;
        }
        else {
            if (m_LogFile.good()) {
                m_LogFile << getTimeString().GetCharString() << "SUCCESS" << std::endl;
                m_LogFile.flush();
            }

            on_flash_window();

            --(*m_ActiveAnchor);

            if (m_ActiveAnchor->GetRemainingPurchases() < 1) {
                m_PurchaseState = PurchaseState::RemovingAnchor;
            }
            else {
                m_PurchaseState = PurchaseState::RestartingAnchor;
            }
        }
    }
    else if (m_PurchaseState == PurchaseState::MoneyCheckFail) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "money check FAILED" << std::endl;
            m_LogFile.flush();
        }

        m_PurchaseState = PurchaseState::RestartingAnchor;
    }
    else if (m_PurchaseState == PurchaseState::RemovingAnchor) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "removing anchor: " << m_ActiveAnchor->GetSearchQuery().GetCharString() << std::endl;
            m_LogFile.flush();
        }

        m_Anchors.Remove(m_ActiveAnchor->GetSearchQuery());

        delete m_ActiveAnchor;

        m_ActiveAnchor = 0;
        m_PurchaseState = PurchaseState::ReadyToPurchase;
    }
    else if (m_PurchaseState == PurchaseState::RestartingAnchor) {
        if (m_LogFile.good()) {
            m_LogFile << getTimeString().GetCharString() << "restarting anchor: " << m_ActiveAnchor->GetSearchQuery().GetCharString() << " [" << m_ActiveAnchor->GetRemainingPurchases() << "]" << std::endl;
            m_LogFile.flush();
        }

        m_ActiveAnchor->Restart();

        m_ActiveAnchor = 0;
        m_PurchaseState = PurchaseState::ReadyToPurchase;
    }
}

Application::Application(int &argc, char **argv)
{
    m_Core = 0;
    m_Session = 0;
    m_Tab = 0;

    m_ActiveAnchor = 0;
    m_DisplayThread = 0;
    m_Runner = 0;
    m_Window = 0;

    // pre initialization
    m_ErrorCount = 0;
    m_LogFile.open("log.txt", std::ofstream::out);
    m_PurchaseState = PurchaseState::ReadyToPurchase;
}
Application::~Application()
{
    m_LogFile.close();
}

int Application::Execute()
{
    on_init();
    on_start();

    while (m_Runner->IsRunning()) {
        on_update();

        Sleep(1000 / 30);
    }

    on_close();
    on_cleanup();
    
    return 0;
}

TL::String getTimeString()
{
    SYSTEMTIME system_time;
    GetLocalTime(&system_time);

    char str[256];
    wsprintfA(str, "[%u/%u/%u %02u:%02u.%02u.%03u] :: ", system_time.wDay, system_time.wMonth, system_time.wYear, system_time.wHour, system_time.wMinute, system_time.wSecond, system_time.wMilliseconds);

    return TL::String(str);
}
